Security Hubのアラートを Microsoft Teams に通知する仕組みをCloudFormationテンプレート化

Security Hubのアラートを Microsoft Teams に通知する仕組みをCloudFormationテンプレート化

Clock Icon2024.03.26

はじめに

Security Hubのアラートを Microsoft Teams に通知する仕組みを CloudFormation テンプレートで作成しました。

以前、Security Hub で検知した内容を Amazon EventBridge 経由で Microsoft Teams に通知する仕組みの構築方法を執筆しました。

今回は、通知する仕組みをCloudFormationテンプレートで構築できるようにしましたので、紹介します。

通知される画面は、以下の通りです。

個人的にEventBridge API送信先やEventBridge 接続をテンプレートで作成することがなかったので、勉強になりました。

構成図

構成は以下の通りです。

作成されるリソース

CloudFormationで作成するリソースは、以下の通りです。

  • EventBridge
    • API送信先
    • 接続
    • ルール
  • IAMロール
  • IAMポリシー

テンプレートで、EventBridge API 送信先とEventBridge 接続を作成すると、以下の2点のリソースが自動作成されます

  1. AWS Secrets Managerのシークレット
  2. サービスリンクロール(AWSServiceRoleForAmazonEventBridgeApiDestinations

1点目は、EventBridge 接続の情報を保存するSecrets Managerのシークレットが自動作成されます。

マネジメントコンソール上では、EventBridge 接続から自動作成されたSecrets Managerのシークレットに遷移できます。

2点目は、AWSServiceRoleForAmazonEventBridgeApiDestinationsというサービスリンクロールが自動作成されます。

EventBridgeが、Secrets Managerのシークレットを作成して保存するために、サービスリンクされたロールを使用します。

ロールには、AmazonEventBridgeApiDestinationsServiceRolePolicyというポリシーがアタッチされています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:CreateSecret",
                "secretsmanager:UpdateSecret",
                "secretsmanager:DescribeSecret",
                "secretsmanager:DeleteSecret",
                "secretsmanager:GetSecretValue",
                "secretsmanager:PutSecretValue"
            ],
            "Resource": "arn:aws:secretsmanager:*:*:secret:events!connection/*"
        }
    ]
}

ちなみに、スタック削除した際、Secrets Managerのシークレットは自動削除され、サービスリンクロールは残ります。

テンプレート

テンプレート内容は以下の通りです

コード (クリックすると展開します)
AWSTemplateFormatVersion: '2010-09-09'
Description: ''
Parameters:
  SystemPrefix:
    Type: String
  EnvPrefix:
    Type: String
  WebhookURL:
    Type: String
  MentionedUserMailAddress:
    Description: [email protected]
    Type: String
  MentionedUserName:
    Type: String

Resources:
  IAMManagedPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: EventBridgePolicyForSecurityHubNotifytoTeams
      Path: /service-role/
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - events:InvokeApiDestination
            Resource:
              - !GetAtt EventsApiDestination.Arn

  IAMRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /service-role/
      RoleName: !Sub ${SystemPrefix}-${EnvPrefix}-eventbridge-teams-api-dest-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sts:AssumeRole
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - !Ref IAMManagedPolicy

  EventsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub ${SystemPrefix}-${EnvPrefix}-securityhub-notify-teams
      EventPattern:
        detail-type:
          - Security Hub Findings - Imported
        source:
          - aws.securityhub
        detail:
          findings:
            Severity:
              Label:
                - HIGH
                - CRITICAL
            ProductName:
              - Security Hub
      State: ENABLED
      Targets:
        - Arn: !GetAtt EventsApiDestination.Arn
          HttpParameters:
            HeaderParameters: {}
            QueryStringParameters: {}
          Id: EventsRuleName
          InputTransformer:
            InputPathsMap:
              AwsAccountId: '$.detail.findings[0].AwsAccountId'
              FirstObservedAt: '$.detail.findings[0].FirstObservedAt'
              LastObservedAt: '$.detail.findings[0].LastObservedAt'
              RecommendationUrl: '$.detail.findings[0].ProductFields.RecommendationUrl'
              Region: '$.detail.findings[0].Resources[0].Region'
              ResourceId: '$.detail.findings[0].Resources[0].Id'
              ResourceType: '$.detail.findings[0].Resources[0].Type'
              SeverityLabel: '$.detail.findings[0].Severity.Label'
              Title: '$.detail.findings[0].Title'
            InputTemplate: !Sub |
              {
                "type": "message",
                "attachments": [
                  {
                    "contentType": "application/vnd.microsoft.card.adaptive",
                    "content": {
                      "type": "AdaptiveCard",
                      "body": [
                        {
                          "type": "TextBlock",
                          "text": "\u003cat\u003e${MentionedUserName}\u003c/at\u003e",
                          "weight": "bolder",
                          "size": "medium"
                        },
                        {
                          "type": "TextBlock",
                          "text": "SecurityHubで重大度<SeverityLabel>のアラートを検知しました",
                          "size": "Large",
                          "weight": "Bolder"
                        },
                        {
                          "type": "Table",
                          "columns": [
                            {
                              "width": 1
                            },
                            {
                              "width": 2
                            }
                          ],
                          "rows": [
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "タイトル",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<Title>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "重要度",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<SeverityLabel>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "修復手順",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "[こちらをクリック](<RecommendationUrl>)"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "アカウントID",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<AwsAccountId>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "リージョン",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<Region>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "リソースタイプ",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<ResourceType>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "リソースID",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<ResourceId>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "初回検出日時",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<FirstObservedAt>"
                                    }
                                  ]
                                }
                              ]
                            },
                            {
                              "type": "TableRow",
                              "cells": [
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "text": "最終検出日時",
                                      "weight": "Bolder"
                                    }
                                  ]
                                },
                                {
                                  "type": "TableCell",
                                  "items": [
                                    {
                                      "type": "TextBlock",
                                      "wrap": true,
                                      "text": "<LastObservedAt>"
                                    }
                                  ]
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
                      "version": "1.5",
                      "msteams": {
                        "width": "full",
                        "entities": [
                          {
                            "type": "mention",
                            "text": "\u003cat\u003e${MentionedUserName}\u003c/at\u003e",
                            "mentioned": {
                              "id": "${MentionedUserMailAddress}",
                              "name": "${MentionedUserName}"
                            }
                          }
                        ]
                      }
                    }
                  }
                ]
              }
          RoleArn: !GetAtt IAMRole.Arn
      EventBusName: default

  EventsConnection:
    Type: AWS::Events::Connection
    Properties:
      Name: !Sub ${SystemPrefix}-${EnvPrefix}-teams-conn
      AuthorizationType: API_KEY
      AuthParameters:
        ApiKeyAuthParameters:
          ApiKeyName: Content-Type
          ApiKeyValue: application/json

  EventsApiDestination:
    Type: AWS::Events::ApiDestination
    Properties:
      Name: !Sub ${SystemPrefix}-${EnvPrefix}-teams-api-dest
      ConnectionArn: !GetAtt EventsConnection.Arn
      InvocationEndpoint: !Ref WebhookURL
      HttpMethod: POST
      InvocationRateLimitPerSecond: 300

Outputs:
  EventsApiDestinationArn:
    Description: The ARN of the EventBridge API Destination
    Value: !GetAtt EventsApiDestination.Arn
    Export:
      Name: EventsApiDestinationArn

今回のテンプレートでは、Teamsに通知される画面は表形式です。

表形式ではなく箇条書きにしたい場合、前回の記事でEventBridgeの入力トランスフォーマーのテンプレートや入力パスを様々紹介していますので、ご参照ください。

CloudFormation スタック作成

パラメータは、以下の値を入れます。

  • SystemPrefix
    • システム名
  • EnvPrefix
    • 環境名
  • WebhookURL
  • MentionedUserMailAddress
    • メンション先のメールアドレス
  • MentionedUserName
    • メンション先の名前

1分半でデプロイが完了しました。

通知の確認

Security Hubのアラートは、以下のように通知されました。

ただし、数回試したところ、同じ通知が重複することが確認されました。

これは、 EventBridgeで起こり得るとAWSドキュメントに記載がありました。

まれに、単一のイベントまたはスケジュールされた期間に対して同じルールが複数回実行されたり、トリガーされる特定のルールに対して同じターゲットを複数回呼び出されたりする場合があります。 引用

重複させない方法もあるようですが、通知の仕組み自体は、重複してもさして問題ないと思います。気になる方は以下もご確認ください。

最後に

本記事では、Security Hubのアラートを Microsoft Teams に通知する仕組みを CloudFormation テンプレートで作成する方法を紹介しました。

複数のアカウントに通知する場合や、アカウントごとにメンション先を変更する必要がある場合に参考になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.